package org.zebra.tenant.config;

import com.alicp.jetcache.SimpleCacheManager;
import com.alicp.jetcache.anno.support.SpringConfigProvider;
import com.alicp.jetcache.autoconfigure.JetCacheAutoConfiguration;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.zebra.beansearch.BeanSearchConfig;
import org.zebra.cache.config.CacheConfig;
import org.zebra.mybatis.plus.config.MybatisPlusConfig;
import org.zebra.mybatis.plus.util.MyBatisPlusUtil;
import org.zebra.redisson.RedissonConfig;
import org.zebra.satoken.SaTokenConfig;
import org.zebra.tenant.TenantHelper;
import org.zebra.tenant.TenantIdAcquirer;
import org.zebra.tenant.beansearch.BeanSearchTenantLineInnerInterceptor;
import org.zebra.tenant.beansearch.BeanSearchTenantSqlInterceptor;
import org.zebra.tenant.jetcache.TenantCacheManager;
import org.zebra.tenant.mybatis.TenantLineHandlerImpl;
import org.zebra.tenant.mybatis.TenantLineInnerPlusInterceptor;
import org.zebra.tenant.redis.TenantNameMapper;
import org.zebra.tenant.redis.TenantStringRedisSerializer;
import org.zebra.tenant.satoken.SaTokenTenantIdAcquirer;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.Parameter;
import springfox.documentation.spring.web.plugins.Docket;

/**
 * @author zhanghongbin
 */
@EnableConfigurationProperties(TenantProperties.class)
@AutoConfiguration(
        before = {JetCacheAutoConfiguration.class},
        after = {MybatisPlusConfig.class, CacheConfig.class})
@ConditionalOnProperty(value = "zebra.tenant.enabled", havingValue = "true", matchIfMissing = true)
public class TenantConfig {

    public TenantConfig(RedisTemplate<String, Object> redisTemplate, StringRedisTemplate stringRedisTemplate) {
        TenantStringRedisSerializer tenantStringRedisSerializer = new TenantStringRedisSerializer();
        redisTemplate.setKeySerializer(tenantStringRedisSerializer);
        redisTemplate.setHashKeySerializer(tenantStringRedisSerializer);
        stringRedisTemplate.setKeySerializer(tenantStringRedisSerializer);
        stringRedisTemplate.setHashKeySerializer(tenantStringRedisSerializer);
    }

    /**
     * mybatis plus 多租户配置
     *
     * @param properties  对象
     * @param interceptor 对象
     * @return 对象
     */
    @Bean
    @ConditionalOnMissingBean
    public TenantLineInnerInterceptor tenantLineInnerInterceptor(
            TenantProperties properties, MybatisPlusInterceptor interceptor) {
        TenantLineInnerPlusInterceptor inner = new TenantLineInnerPlusInterceptor(
                new TenantLineHandlerImpl(properties), properties.isWhereHasTenantId());
        // 放在第一位
        MyBatisPlusUtil.addInterceptor(interceptor, inner, 0);
        return inner;
    }

    /**
     * 使用自定义 CacheManger
     *
     * @param springConfigProvider 对象
     * @return 对象
     */
    @Bean
    public SimpleCacheManager cacheManager(@Autowired SpringConfigProvider springConfigProvider) {
        TenantCacheManager cacheManager = new TenantCacheManager();
        cacheManager.setCacheBuilderTemplate(springConfigProvider.getCacheBuilderTemplate());
        return cacheManager;
    }

    @Bean
    @ConditionalOnMissingClass(value = "org.zebra.satoken.SaTokenConfig")
    @ConditionalOnMissingBean
    public TenantIdAcquirer tenantIdAcquirer() {
        return new TenantIdAcquirer();
    }

    //    @ConditionalOnBean(ForestConfigurationProperties.class)
    //    @AutoConfiguration
    //    static class Forest {
    //        public Forest(ForestConfigurationProperties forestConfigurationProperties) {
    //            forestConfigurationProperties.getInterceptors().add(ForestContextTenantInterceptor.class);
    //        }
    //    }

    @ConditionalOnClass(BeanSearchConfig.class)
    @AutoConfiguration
    static class BeanSearch {
        @DependsOn("tenantLineInnerInterceptor")
        @Bean
        public BeanSearchTenantSqlInterceptor beanSearchTenantSqlInterceptor(
                @Autowired TenantLineInnerInterceptor tenantLineInnerInterceptor) {
            return new BeanSearchTenantSqlInterceptor(
                    new BeanSearchTenantLineInnerInterceptor(tenantLineInnerInterceptor.getTenantLineHandler()));
        }
    }

    @ConditionalOnClass(RedissonConfig.class)
    @AutoConfiguration(after = RedissonConfig.class)
    static class Redisson {

        @Bean
        public RedissonAutoConfigurationCustomizer redissonTenantCustomizer() {
            return config -> {
                if (config.isClusterConfig()) {
                    config.useClusterServers().setNameMapper(new TenantNameMapper());
                } else {
                    // 使用单机模式
                    config.useSingleServer().setNameMapper(new TenantNameMapper());
                }
            };
        }
    }

    /**
     * 不依赖 satoken 框架
     */
    @ConditionalOnClass(SaTokenConfig.class)
    @AutoConfiguration(before = SaTokenConfig.class)
    static class SaToken {

        //            @Bean
        //            @ConditionalOnMissingBean
        //            public SaTokenDao saTokenDao() {
        //                return new TenantSaTokenDaoRedisMsgPack();
        //            }

        @Bean
        @ConditionalOnMissingBean
        public TenantIdAcquirer tenantIdAcquirer() {
            return new SaTokenTenantIdAcquirer();
        }
    }

    @ConditionalOnClass(Docket.class)
    static class Swagger {
        @Bean
        public Parameter swaggerTenantParameter(@Autowired TenantProperties properties) {
            ParameterBuilder tokenPar = new ParameterBuilder();
            tokenPar.name(TenantHelper.HEADER_TENANT_ID)
                    .description("租户id")
                    .modelRef(new ModelRef(properties.getIdType()))
                    .parameterType("header")
                    .required(true)
                    .defaultValue("1");
            return tokenPar.build();
        }
    }
}
